package magnet.processor.registry.instances;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import magnet.internal.InstanceFactory;
import magnet.processor.registry.Model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class InstanceIndexGenerator {

    public CodeBlock generate(Model.Registry registry) {
        List<Model.InstanceFactory> factories = registry.getInstanceFactories();
        List<magnet.processor.registry.instances.Model.Inst> insts = new ArrayList<>();
        for (Model.InstanceFactory factory : factories) {
            magnet.processor.registry.instances.Model.Inst inst = new magnet.processor.registry.instances.Model.Inst(toQualifiedName(factory.getInstanceType()),
                    factory.getClassifier(),
                    factory.getFactoryClass());
            insts.add(inst);
        }
        magnet.processor.registry.instances.Model.Index index = new Indexer().index(insts);
        return CodeBlock.builder()
                .add(generateArrayOfFactoriesCodeBlock(index))
                .add(generateIndexCodeBlock(index))
                .build();
    }

    private CodeBlock generateIndexCodeBlock(magnet.processor.registry.instances.Model.Index index) {
        IndexGeneratorVisitor indexGenerator = new IndexGeneratorVisitor();
        index.accept(indexGenerator);

        int mapSize = Math.max(Math.round(index.getInstances().size() / 0.75f), 16);
        return CodeBlock.builder()
                .addStatement(
                        "$T<$T, $T> index = new $T<>(" + mapSize + ")",
                        Map.class,
                        Class.class,
                        Object.class,
                        HashMap.class
                )
                .add(indexGenerator.getTargetsBuilder().build())
                .add(indexGenerator.getIndexBuilder().build())
                .build();
    }

    private CodeBlock generateArrayOfFactoriesCodeBlock(magnet.processor.registry.instances.Model.Index index) {
        if (index.getInstances().isEmpty()) {
            return CodeBlock.builder()
                    .addStatement("$T[] factories = new $T[0]", InstanceFactory.class, InstanceFactory.class)
                    .build();
        } else {
            CodeBlock.Builder builder = CodeBlock.builder()
                    .add("$T[] factories = new $T[] {", InstanceFactory.class, InstanceFactory.class)
                    .indent();

            for (magnet.processor.registry.instances.Model.Inst inst : index.getInstances()) {
                builder.add("\nnew $T(),", inst.getFactory());
            }

            return builder
                    .unindent()
                    .add("\n};\n")
                    .build();
        }
    }

    private String toQualifiedName(ClassName className) {
        return className.packageName() + "." + className.simpleName();
    }
}
